package mq

import (
	"log/slog"
	"time"

	"github.com/lab-online/pkg/logger"
	amqp "github.com/rabbitmq/amqp091-go"
)

type MessageQueue struct {
	Connection *amqp.Connection
}

type QueueDeclareConfig struct {
	Name       string
	Durable    bool
	AutoDelete bool
	Exclusive  bool
	NoWait     bool
	Args       amqp.Table
}

// Connect 连接 message queue, 失败重试 5 次, 最终失败则 panic
func Connect(url string) *MessageQueue {
	var connection *amqp.Connection
	var err error

	for i := 0; i < 5; i++ {
		connection, err = amqp.Dial(url)
		if err == nil {
			logger.Info("connected message queue", url)
			return &MessageQueue{connection}
		}

		logger.Error("failed to connect message queue, will retry after", i*2, "s")
		time.Sleep(time.Second * time.Duration(i*2))
	}

	logger.Error("failed to connect message queue", url)
	panic(err)
}

func (mq *MessageQueue) Close() {
	mq.Connection.Close()
}

// Channel 新建 channel, 错误转为常量错误
func (mq *MessageQueue) Channel() (*amqp.Channel, error) {
	if channel, err := mq.Connection.Channel(); err != nil {
		slog.Error("channel open error", slog.String("msg", err.Error()))
		return nil, ErrChannel
	} else {
		return channel, nil
	}
}

// DeclareQueue 新建 channel 并安全声明队列, 错误转为常量错误, 记录日志
func (mq *MessageQueue) DeclareQueue(
	config QueueDeclareConfig,
) (*amqp.Channel, *amqp.Queue, error) {
	if channel, err := mq.Channel(); err != nil {
		return nil, nil, err
	} else if queue, err := mq.DeclareQueueWithChannel(channel, config); err != nil {
		return nil, nil, err
	} else {
		return channel, queue, nil
	}
}

// DeclareQueueWithChannel 使用已有 channel 声明队列, 错误转为常量错误, 记录日志
func (mq *MessageQueue) DeclareQueueWithChannel(
	channel *amqp.Channel, config QueueDeclareConfig,
) (*amqp.Queue, error) {
	if queue, err := channel.QueueDeclare(
		config.Name,
		config.Durable,
		config.AutoDelete,
		config.Exclusive,
		config.NoWait,
		config.Args,
	); err != nil {
		slog.Error(
			"queue declare error",
			slog.String("queue", config.Name),
			slog.String("msg", err.Error()),
		)
		return nil, ErrDeclareExchange
	} else {
		return &queue, nil
	}
}
